﻿using Fun.IO;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Fun.FontDecode.OpenTypes
{
    // https://www.jianshu.com/p/21ae2dc5c50a
    // https://docs.microsoft.com/zh-cn/typography/opentype/spec/otff
    // https://developer.apple.com/fonts/TrueType-Reference-Manual/
    public class OpenTypeParser : ParserBase
    {
        static IDictionary<string, Func<OpenTypeParser, BinaryReader, object>> TableDataFuncs
            = new Dictionary<string, Func<OpenTypeParser, BinaryReader, object>>
        {
            { "head", head }, { "maxp", maxp }, { "loca", loca },
            { "cmap", cmap }, { "glyf", glyf }, //{ "name", name },
        };

        Dictionary<string, TableRecord> FTableRecords = new Dictionary<string, TableRecord>();
        Dictionary<string, object> FTableObjects = new Dictionary<string, object>();

        public class OffsetTable
        {
            // 0x00010000 or 0x4F54544F('OTTO')
            // TrueType outlines should use the value of 0x00010000
            // OpenType fonts containing CFF data (version 1 or 2) should use 0x4F54544F
            public uint SfntVersion { get; internal set; }
            // Number of tables
            public ushort NumTables { get; internal set; }
            // 	searchRange (Maximum power of 2 <= numTables) x 16
            public ushort SearchRange { get; internal set; }
            // Log2(maximum power of 2 <= numTables).
            public ushort EntrySelector { get; internal set; }
            // NumTables x 16 - searchRange
            public ushort RangeShift { get; internal set; }
        }

        public class TableRecord
        {
            // Table identifier
            // Tag data type: Array of four uint8s (length = 32 bits) used to identify a table
            public byte[] TableTag { get; internal set; }
            public string TableName { get; internal set; }
            // CheckSum for this table.
            public uint CheckSum { get; internal set; }
            // Offset from beginning of TrueType font file.
            public uint Offset { get; internal set; }
            // Length of this table
            public uint Length { get; internal set; }
        }

        public override void Parse(BinaryReader reader)
        {
            var table = new OffsetTable();

            // 0x00010000 or 0x4F54544F('OTTO')
            // TrueType outlines should use the value of 0x00010000
            // OpenType fonts containing CFF data (version 1 or 2) should use 0x4F54544F
            table.SfntVersion = reader.ReadUInt32().Reverse();
            if (table.SfntVersion != 0x00010000 && table.SfntVersion != 0x4F54544F)
                throw new Exception($"OpenType：不支持此版本的文件。)");
            // Number of tables
            table.NumTables = reader.ReadUInt16().Reverse();
            // 	searchRange (Maximum power of 2 <= numTables) x 16
            table.SearchRange = reader.ReadUInt16().Reverse();
            // Log2(maximum power of 2 <= numTables).
            table.EntrySelector = reader.ReadUInt16().Reverse();
            // NumTables x 16 - searchRange
            table.RangeShift = reader.ReadUInt16().Reverse();
            if (table.RangeShift != table.NumTables * 16 - table.SearchRange)
                throw new Exception("OpenType：表范围不匹配。");
            // table records
            for (var i = 0; i < table.NumTables; i++)
            {
                var rec = new TableRecord();

                // Table identifier
                // Tag data type: Array of four uint8s (length = 32 bits) used to identify a table
                rec.TableTag = reader.ReadBytes(1 * 4);
                rec.TableName = Encoding.ASCII.GetString(rec.TableTag);
                // CheckSum for this table.
                rec.CheckSum = reader.ReadUInt32().Reverse();
                // Offset from beginning of TrueType font file.
                rec.Offset = reader.ReadUInt32().Reverse();
                // Length of this table
                rec.Length = reader.ReadUInt32().Reverse();

                FTableRecords.Add(rec.TableName, rec);
            }

            Call("head", reader);
            Call("maxp", reader);
            Call("loca", reader);
            Call("glyf", reader);
            Call("cmap", reader);
            Call("name", reader);
        }

        void Call(string name, BinaryReader reader)
        {
            TableRecord rec;
            Func<OpenTypeParser, BinaryReader, object> proc;
            if (!FTableRecords.TryGetValue(name, out rec) || rec.Length == 0)
                return;
            if (!TableDataFuncs.TryGetValue(name, out proc) || proc == null)
                return;

            using (var r = new BinaryReader(new ScopedInputStream(reader.BaseStream, rec.Offset, rec.Length)))
            {
                var result = proc(this, r);
                FTableObjects.Add(name, result);
            }
        }

        public T Get<T>(string name)
        {
            object result;
            return FTableObjects.TryGetValue(name, out result) ? (T)result : default(T);
        }

        #region head
        // https://docs.microsoft.com/zh-cn/typography/opentype/spec/head

        public class FontHeaderTable
        {
            // Major version number of the font header table — set to 1
            public ushort MajorVersion { get; internal set; }
            // Minor version number of the font header table — set to 0
            public ushort MinorVersion { get; internal set; }
            // Fixed / Set by font manufacturer
            public int FontRevision { get; internal set; }
            // compute: set it to 0, sum the entire font as uint32, then store 0xB1B0AFBA - sum
            public uint CheckSumAdjustment { get; internal set; }
            // 0x5F0F3CF5
            public uint MagicNumber { get; internal set; }
            // Flags
            public ushort Flags { get; internal set; }
            // from 16 to 16384. Any value in this range is valid
            // In fonts that have TrueType outlines, a power of 2 is recommended
            public ushort UnitsPerEm { get; internal set; }
            // LONGDATETIME / Number of seconds since 12:00 midnight that started January 1st 1904 in GMT/UTC time zone
            public long CreatedTime { get; internal set; }
            // LONGDATETIME / Number of seconds since 12:00 midnight that started January 1st 1904 in GMT/UTC time zone. 64-bit integer
            public long ModifiedTime { get; internal set; }
            // For all glyph bounding boxes
            public ushort MinX { get; internal set; }
            public ushort MinY { get; internal set; }
            public ushort MaxX { get; internal set; }
            public ushort MaxY { get; internal set; }
            // Bit 0-6: Bold/Italic/Underline/Outline/Shadow/Condensed/Extended
            // Bits 7–15: Reserved(set to 0).
            public ushort MacStyle { get; internal set; }
            // Smallest readable size in pixels
            public ushort LowestRecPPEM { get; internal set; }
            // Deprecated(Set to 2)
            // 0: Fully mixed directional glyphs{ get; internal set; }
            //1: Only strongly left to right{ get; internal set; }
            //2: Like 1 but also contains neutrals{ get; internal set; }
            //-1: Only strongly right to left{ get; internal set; }
            //-2: Like -1 but also contains neutrals.
            public short FontDirectionHint { get; internal set; }
            // 0 for short offsets(Offset16), 1 for long (Offset32)
            public short IndexToLocFormat { get; internal set; }
            // 0 for current format
            public short GlyphDataFormat { get; internal set; }
        }

        static object head(OpenTypeParser owner, BinaryReader r)
        {
            var table = new FontHeaderTable();

            // Major version number of the font header table — set to 1
            table.MajorVersion = r.ReadUInt16().Reverse();
            // Minor version number of the font header table — set to 0
            table.MinorVersion = r.ReadUInt16().Reverse();
            if (table.MajorVersion != 1 && table.MinorVersion != 0)
                throw new Exception("OpenType：不支持此版本的文件(head)。");
            // Fixed / Set by font manufacturer
            table.FontRevision = r.ReadInt32().Reverse();
            // compute: set it to 0, sum the entire font as uint32, then store 0xB1B0AFBA - sum
            table.CheckSumAdjustment = r.ReadUInt32().Reverse();
            // 0x5F0F3CF5
            table.MagicNumber = r.ReadUInt32().Reverse();
            if (table.MagicNumber != 0x5F0F3CF5)
                throw new Exception("OpenType：非法的文件格式(head)。");
            table.Flags = r.ReadUInt16().Reverse();
            // from 16 to 16384. Any value in this range is valid
            // In fonts that have TrueType outlines, a power of 2 is recommended
            table.UnitsPerEm = r.ReadUInt16().Reverse();
            // LONGDATETIME / Number of seconds since 12:00 midnight that started January 1st 1904 in GMT/UTC time zone
            table.CreatedTime = r.ReadInt64().Reverse();
            // LONGDATETIME / Number of seconds since 12:00 midnight that started January 1st 1904 in GMT/UTC time zone. 64-bit integer
            table.ModifiedTime = r.ReadInt64().Reverse();
            // For all glyph bounding boxes
            table.MinX = r.ReadUInt16().Reverse();
            table.MinY = r.ReadUInt16().Reverse();
            table.MaxX = r.ReadUInt16().Reverse();
            table.MaxY = r.ReadUInt16().Reverse();
            // Bit 0-6: Bold/Italic/Underline/Outline/Shadow/Condensed/Extended
            // Bits 7–15: Reserved(set to 0).
            table.MacStyle = r.ReadUInt16().Reverse();
            // Smallest readable size in pixels
            table.LowestRecPPEM = r.ReadUInt16().Reverse();
            // Deprecated(Set to 2)
            // 0: Fully mixed directional glyphs;
            //1: Only strongly left to right;
            //2: Like 1 but also contains neutrals;
            //-1: Only strongly right to left;
            //-2: Like -1 but also contains neutrals.
            table.FontDirectionHint = r.ReadInt16().Reverse();
            // 0 for short offsets(Offset16), 1 for long (Offset32)
            table.IndexToLocFormat = r.ReadInt16().Reverse();
            // 0 for current format
            table.GlyphDataFormat = r.ReadInt16().Reverse();

            return table;
        }
        #endregion head

        #region maxp
        // https://docs.microsoft.com/zh-cn/typography/opentype/spec/maxp

        public class MaximumProfile
        {
            // Fixed / version 0x00005000 for version 0.5
            public int Version { get; internal set; }
            // The number of glyphs in the font.
            public ushort NumGlyphs { get; internal set; }

            // 1.0
            // Maximum points in a non-composite glyph
            public ushort MaxPoints { get; internal set; }
            // Maximum contours in a non-composite glyph
            public ushort MaxContours { get; internal set; }
            // Maximum points in a composite glyph
            public ushort MaxCompositePoints { get; internal set; }
            // Maximum contours in a composite glyph
            public ushort MaxCompositeContours { get; internal set; }
            // 1 if instructions do not use the twilight zone (Z0), 
            // or 2 if instructions do use Z0; should be set to 2 in most cases.
            public ushort MaxZones { get; internal set; }
            // Maximum points used in Z0.
            public ushort MaxTwilightPoints { get; internal set; }
            // Number of Storage Area locations
            public ushort MaxStorage { get; internal set; }
            // Number of FDEFs, equal to the highest function number + 1
            public ushort MaxFunctionDefs { get; internal set; }
            // Number of IDEFs
            public ushort MaxInstructionDefs { get; internal set; }
            // Maximum stack depth across Font Program ('fpgm' table), CVT Program('prep' table) and all glyph instructions(in the 'glyf' table)
            public ushort MaxStackElements { get; internal set; }
            // Maximum byte count for glyph instructions
            public ushort MaxSizeOfInstructions { get; internal set; }
            // Maximum number of components referenced at “top level” for any composite glyph
            public ushort MaxComponentElements { get; internal set; }
            // Maximum levels of recursion; 1 for simple components
            public ushort MaxComponentDepth { get; internal set; }
        }

        static object maxp(OpenTypeParser owner, BinaryReader r)
        {
            var profile = new MaximumProfile();

            profile.Version = r.ReadInt32().Reverse();
            if (profile.Version != 0x00005000 && profile.Version != 0x00010000)
                throw new Exception("OpenType：不支持此版本的文件(maxp)。");

            // The number of glyphs in the font
            profile.NumGlyphs = r.ReadUInt16().Reverse();
            // 1.0
            if (profile.Version == 0x00010000)
            {
                // Maximum points in a non-composite glyph
                profile.MaxPoints = r.ReadUInt16();
                // Maximum contours in a non-composite glyph
                profile.MaxContours = r.ReadUInt16();
                // Maximum points in a composite glyph
                profile.MaxCompositePoints = r.ReadUInt16();
                // Maximum contours in a composite glyph
                profile.MaxCompositeContours = r.ReadUInt16();
                // 1 if instructions do not use the twilight zone (Z0), 
                // or 2 if instructions do use Z0; should be set to 2 in most cases.
                profile.MaxZones = r.ReadUInt16();
                // Maximum points used in Z0.
                profile.MaxTwilightPoints = r.ReadUInt16();
                // Number of Storage Area locations
                profile.MaxStorage = r.ReadUInt16();
                // Number of FDEFs, equal to the highest function number + 1
                profile.MaxFunctionDefs = r.ReadUInt16();
                // Number of IDEFs
                profile.MaxInstructionDefs = r.ReadUInt16();
                // Maximum stack depth across Font Program ('fpgm' table), CVT Program('prep' table) and all glyph instructions(in the 'glyf' table)
                profile.MaxStackElements = r.ReadUInt16();
                // Maximum byte count for glyph instructions
                profile.MaxSizeOfInstructions = r.ReadUInt16();
                // Maximum number of components referenced at “top level” for any composite glyph
                profile.MaxComponentElements = r.ReadUInt16();
                // Maximum levels of recursion; 1 for simple components
                profile.MaxComponentDepth = r.ReadUInt16();
            }

            return profile;
        }
        #endregion maxp

        #region loca
        // https://docs.microsoft.com/zh-cn/typography/opentype/spec/loca

        public class IndexToLocation
        {
            // The actual local offset divided by 2 is stored. The value of n is numGlyphs + 1
            // The value for numGlyphs is found in the 'maxp' table.
            public uint[] Offsets { get; internal set; }
        }

        static object loca(OpenTypeParser owner, BinaryReader r)
        {
            var table = owner.Get<FontHeaderTable>("head");
            var profile = owner.Get<MaximumProfile>("maxp");
            if (table == null || profile == null)
                return null;

            var location = new IndexToLocation();
            location.Offsets = new uint[profile.NumGlyphs + 1];
            switch (table.IndexToLocFormat)
            {
                case 0:
                    for (var i = 0; i <= profile.NumGlyphs; i++)
                        location.Offsets[i] = (uint)r.ReadUInt16().Reverse() * 2;
                    break;
                case 1:
                    for (var i = 0; i < profile.NumGlyphs; i++)
                        location.Offsets[i] = r.ReadUInt32().Reverse();
                    break;
                default:
                    throw new Exception("OpenType：不支持此版本的文件(loca)。");
            }

            return location;
        }
        #endregion loca

        #region glyf
        // https://docs.microsoft.com/zh-cn/typography/opentype/spec/glyf

        public class GlyphHeader
        {
            // >= 0 is a simple glyph, value -1 should be used for composite glyphs
            public short NumberOfContours { get; internal set; }
            // coordinate data
            public short MinX { get; internal set; }
            public short MinY { get; internal set; }
            public short MaxX { get; internal set; }
            public short MaxY { get; internal set; }

            public ushort[] ContourEnds { get; internal set; }
            public Coordinate[] Points { get; internal set; }
            public Component[] Components { get; internal set; }
            // calculated
            public string DataString { get; internal set; }
        }

        static object glyf(OpenTypeParser owner, BinaryReader r)
        {
            var location = owner.Get<IndexToLocation>("loca");
            if (location == null)
                return null;

            GlyphHeader last = null; 
            var array = new GlyphHeader[location.Offsets.Length - 1];
            for (var i = 0; i < array.Length; i++)
            {
                var offset = (int)location.Offsets[i];
                var next = (int)location.Offsets[i + 1];

                r.BaseStream.Position = offset;
                // >= 0 is a simple glyph, value -1 should be used for composite glyphs
                var header = new GlyphHeader();
                header.NumberOfContours = r.ReadInt16().Reverse();
                header.MinX = r.ReadInt16().Reverse();
                header.MinY = r.ReadInt16().Reverse();
                header.MaxX = r.ReadInt16().Reverse();
                header.MaxY = r.ReadInt16().Reverse();
                // glyph data
                if (offset == next)
                {
                    array[i] = last;
                    continue;
                }

                if (header.NumberOfContours >= 0)
                    SimpleGlyph(r, header);
                else if (header.NumberOfContours < 0)
                    CompositeGlyph(r, header);

                array[i] = header;
                last = header;
            }

            return array;
        }

        [Flags]
        public enum SimpleGlyphFlags
        {
            ON_CURVE = 1,
            X_IS_BYTE = 2,
            Y_IS_BYTE = 4,
            REPEAT = 8,
            X_DELTA = 16,
            Y_DELTA = 32,
        }

        public class Coordinate
        {
            public bool IsOnCurve { get; internal set; }
            public int X { get; internal set; }
            public int Y { get; internal set; }

            public override string ToString()
            {
                return $"{X},{Y}{(IsOnCurve?"":",0")}";
            }

            public static Coordinate FromString(string s)
            {
                var a = s.Split(',');
                return new Coordinate()
                {
                    X = int.Parse(a[0]),
                    Y = int.Parse(a[1]),
                    IsOnCurve = a.Length < 3 || a[2] != "0",
                };
            }

            public static string ToString(Coordinate[] arr)
            {
                var sb = new StringBuilder();
                arr.Aggregate(sb, (a, b) => a.Append("|").Append(b));
                return sb.ToString(1, sb.Length - 1);
            }

            public static Coordinate[] FromStrings(string s)
            {
                return s.Split('|')
                    .Select(x => FromString(x))
                    .ToArray();
            }
        }

        static void ReadCoordinates(BinaryReader r, 
            Coordinate[] points, Action<Coordinate, int> set, 
            SimpleGlyphFlags[] flags, SimpleGlyphFlags byteFlag, SimpleGlyphFlags deltaFlag, int min, int max)
        {
            var value = 0;
            for (var i = 0; i < flags.Length; i++)
            {
                var flag = flags[i];
                if (flag.HasFlag(byteFlag))
                {
                    if (flag.HasFlag(deltaFlag)) // bit1/2: 1, bit4/5: 1，表示是正数
                        value += r.ReadByte();
                    else // bit1/2: 1, bit4/5: 0，表示是负数
                        value -= r.ReadByte();
                }
                else if (!flag.HasFlag(deltaFlag)) // bit1/2: 0, bit4/5: 0，表示坐标相对于上一坐标
                    value += r.ReadInt16().Reverse();
                else // bit1/2: 0, bit4/5: 1，表示坐标与上一坐标相同
                { }
                
                set(points[i], value);
            }
        }

        static void SimpleGlyph(BinaryReader r, GlyphHeader header)
        {
            header.ContourEnds = new ushort[header.NumberOfContours];
            for (var i = 0; i < header.NumberOfContours; i++)
                header.ContourEnds[i] = r.ReadUInt16().Reverse();

            // skip over intructions
            var offset = r.ReadUInt16().Reverse();
            r.BaseStream.Position += offset;
            if (header.NumberOfContours == 0)
                return;

            var numPoints = header.ContourEnds[header.NumberOfContours - 1] + 1;
            var flags = new SimpleGlyphFlags[numPoints];
            var points = new Coordinate[numPoints];
            for (var i = 0; i < numPoints; i++)
            {
                var flag = (SimpleGlyphFlags)r.ReadByte();
                flags[i] = flag;
                points[i] = new Coordinate()
                {
                    IsOnCurve = flag.HasFlag(SimpleGlyphFlags.ON_CURVE),
                };

                if (flag.HasFlag(SimpleGlyphFlags.REPEAT))
                {
                    var repeatCount = (int)r.ReadByte();
                    while (repeatCount-- > 0)
                    {
                        i++;
                        flags[i] = flag;
                        points[i] = new Coordinate()
                        {
                            IsOnCurve = flag.HasFlag(SimpleGlyphFlags.ON_CURVE),
                        };
                    }
                }
            }

            ReadCoordinates(r, points, (p, v) => p.X = v,
                flags, SimpleGlyphFlags.X_IS_BYTE, SimpleGlyphFlags.X_DELTA, header.MinX, header.MaxX);
            ReadCoordinates(r, points, (p, v) => p.Y = v,
                flags, SimpleGlyphFlags.Y_IS_BYTE, SimpleGlyphFlags.Y_DELTA, header.MinX, header.MaxY);
            header.Points = points;

            // calulate hash
            var polyPoints = GetGlyphPolygon(header);
            header.DataString = Coordinate.ToString(polyPoints);
        }

        [Flags]
        public enum CompositeGlyphFlags
        {
            ARG_1_AND_2_ARE_WORDS = 1,
            ARGS_ARE_XY_VALUES = 2,
            ROUND_XY_TO_GRID = 4,
            WE_HAVE_A_SCALE = 8,
            RESERVED = 16,
            MORE_COMPONENTS = 32,
            WE_HAVE_AN_X_AND_Y_SCALE = 64,
            WE_HAVE_A_TWO_BY_TWO = 128,
            WE_HAVE_INSTRUCTIONS = 256,
            USE_MY_METRICS = 512,
            OVERLAP_COMPONENT = 1024,
        }

        public class Component
        {
            public int GlyphIndex { get; internal set; }
            // a/b/c/d/e/f
            // xscale / scale01 /scale10 / yscale
            public int[] Matrics { get; internal set; }

            public uint SrcPointIndex { get; internal set; }
            public uint DestPointIndex { get; internal set; }
        }

        static int Read2_14(BinaryReader r)
        {
            return r.ReadInt16().Reverse() / (1 << 14);
        }

        static void CompositeGlyph(BinaryReader r, GlyphHeader header)
        {
            var components = new List<Component>();
            var flag = CompositeGlyphFlags.MORE_COMPONENTS;
            while (flag.HasFlag(CompositeGlyphFlags.MORE_COMPONENTS))
            {
                int arg1, arg2;
                flag = (CompositeGlyphFlags)r.ReadUInt16().Reverse();
                var component = new Component()
                {
                    GlyphIndex = r.ReadUInt16().Reverse(),
                    Matrics = new int[] { 1, 0, 0, 1, 0, 0 }
                };

                if (flag.HasFlag(CompositeGlyphFlags.ARG_1_AND_2_ARE_WORDS))
                {
                    arg1 = r.ReadInt16().Reverse();
                    arg2 = r.ReadInt16().Reverse();
                }
                else
                {
                    arg1 = r.ReadByte();
                    arg2 = r.ReadByte();
                }

                if (flag.HasFlag(CompositeGlyphFlags.WE_HAVE_A_SCALE))
                {
                    // a/d
                    component.Matrics[0] = r.ReadUInt16().Reverse(); // Format 2.14 // Read2_14(r);
                    component.Matrics[3] = component.Matrics[0];
                }
                else if (flag.HasFlag(CompositeGlyphFlags.WE_HAVE_AN_X_AND_Y_SCALE))
                {
                    // a/d
                    component.Matrics[0] = r.ReadUInt16().Reverse(); // Format 2.14 // Read2_14(r);
                    component.Matrics[3] = r.ReadUInt16().Reverse(); // Format 2.14 // Read2_14(r);
                }
                else if (flag.HasFlag(CompositeGlyphFlags.WE_HAVE_A_TWO_BY_TWO))
                {
                    // a/b/c/d
                    component.Matrics[0] = r.ReadUInt16().Reverse(); // Format 2.14 // Read2_14(r);
                    component.Matrics[1] = r.ReadUInt16().Reverse(); // Format 2.14 // Read2_14(r);
                    component.Matrics[2] = r.ReadUInt16().Reverse(); // Format 2.14 // Read2_14(r);
                    component.Matrics[3] = r.ReadUInt16().Reverse(); // Format 2.14 // Read2_14(r);
                }

                if (flag.HasFlag(CompositeGlyphFlags.ARGS_ARE_XY_VALUES))
                {
                    // e/f
                    component.Matrics[4] = arg1;
                    component.Matrics[5] = arg2;

                    // DecodeGlyph(glyphIndex, points, matrics);
                }
                else
                {
                    component.DestPointIndex = (uint)arg1;
                    component.SrcPointIndex = (uint)arg2;
                }

                components.Add(component);
            }

            header.Components = components.ToArray();
            if (flag.HasFlag(CompositeGlyphFlags.WE_HAVE_INSTRUCTIONS))
                r.BaseStream.Position += r.ReadUInt16().Reverse();

            // calulate hash
            header.DataString = "";
        }
        
        public static Coordinate[] GetGlyphPolygon(GlyphHeader header)
        {
            if (header.NumberOfContours == 0)
                return new Coordinate[0];
            
            int p = 0, c = 0;
            var pts = new List<Coordinate>();
            Coordinate first = null;
            while (p < header.Points.Length)
            {
                var point = header.Points[p];
                pts.Add(point);
                if (first == null)
                    first = point;

                if (p == header.ContourEnds[c])
                {
                    c += 1;
                    if (first != null)
                        pts.Add(first);
                    first = null;
                }

                p += 1;
            }
            if (first != null)
                pts.Add(first);

            return pts.ToArray();
        }

        const int LineSpacing = 8;

        public Size CalulateDisplaySize(Graphics g, Rectangle r, int size, Font nameFont)
        {
            var head = Get<FontHeaderTable>("head");
            var cmap = Get<IndexMappingTable>("cmap");

            var scale = size * 1f / head.UnitsPerEm;
            var width = (int)((head.MaxX - head.MinX) * scale);
            var height = (int)((head.MaxY - head.MinY) * scale);
            var textHeight = (int)g.MeasureString("A", nameFont).Height;
            var count = cmap.CharMaps.Select(x => x.EndCharCode - x.StartCharCode + 1).Sum(x => x);
            var cols = count == 0 ? 0 : (int)Math.Min(count, r.Width / width);
            var rows = count == 0 ? 0 : (int)((count + cols - 1) / cols);

            return new Size(cols * width, rows * (height + textHeight + LineSpacing));
        }

        public void DrawGlyphs(Graphics g, Rectangle r, int size, 
            string nameFormat, Font nameFont, Brush nameBrush, Brush fontBrush)
        {
            var glyf = Get<GlyphHeader[]>("glyf");
            var head = Get<FontHeaderTable>("head");
            var cmap = Get<IndexMappingTable>("cmap");

            var scale = size * 1f / head.UnitsPerEm;
            var width = head.MaxX - head.MinX;
            var height = head.MaxY - head.MinY;
            var textHeight = (int)g.MeasureString("A", nameFont).Height;
            var location = r.Location;
            using (var bmp = new Bitmap((int)(width * scale), (int)(height * scale)))
            {
                using (var bg = Graphics.FromImage(bmp))
                {
                    bg.ScaleTransform(scale, -scale);
                    bg.TranslateTransform(-head.MinX, -head.MinY - height);
                    foreach (var chmap in cmap.CharMaps)
                    {
                        // 绘制图形
                        var gh = glyf[chmap.StartGlyphID];
                        var points = GetGlyphPolygon(gh).Select(x => new Point(x.X, x.Y)).ToArray();
                        // font
                        bg.Clear(Color.Transparent);
                        bg.FillPolygon(fontBrush, points);

                        for (var ch = chmap.StartCharCode; ch <= chmap.EndCharCode; ch++)
                        {
                            // font
                            g.DrawImage(bmp, location);
                            // text
                            var name = string.Format(nameFormat, (int)ch);
                            g.DrawString(name, nameFont, nameBrush,
                                new Point(location.X, location.Y + bmp.Height));

                            // transform
                            location.X += bmp.Width;
                            if (location.X + bmp.Width > r.Width)
                            {
                                location.X = r.Left;
                                location.Y += bmp.Height + textHeight + LineSpacing;
                            }
                        }
                    }
                }
            }
        }
        #endregion glyf

        #region cmap
        // https://docs.microsoft.com/zh-cn/typography/opentype/spec/cmap

        public class IndexMappingTable
        {
            // Table version number(0)
            public ushort Version { get; internal set; }
            // Number of encoding tables that follow
            public ushort NumTables { get; internal set; }
            //
            public EncodingRecord[] EncodingRecords { get; internal set; }

            // calculated
            public SequentialMapGroup[] CharMaps { get; internal set; }
        }

        public class EncodingRecord
        {
            // Platform ID
            public ushort PlatformID { get; internal set; }
            // Platform-specific encoding ID
            public ushort EncodingID { get; internal set; }
            // Byte offset from beginning of table to the subtable for this encoding
            public uint Offset { get; internal set; }
        }

        public class SequentialMapGroup
        {
            // First character code in this group
            public uint StartCharCode { get; internal set; }
            // Last character code in this group
            public uint EndCharCode { get; internal set; }
            // Glyph index corresponding to the starting character code
            public uint StartGlyphID { get; internal set; }
        }

        static object cmap(OpenTypeParser owner, BinaryReader r)
        {
            var table = new IndexMappingTable();

            // Table version number(0)
            table.Version = r.ReadUInt16().Reverse();
            // Number of encoding tables that follow
            table.NumTables= r.ReadUInt16().Reverse();
            //
            table.EncodingRecords = new EncodingRecord[table.NumTables];
            // load tables
            for (var i = 0; i < table.NumTables; i++)
            {
                var rec = new EncodingRecord();

                // Platform ID
                rec.PlatformID = r.ReadUInt16().Reverse();
                // Platform - specific encoding ID
                rec.EncodingID = r.ReadUInt16().Reverse();
                // Byte offset from beginning of table to the subtable for this encoding
                rec.Offset = r.ReadUInt32().Reverse();

                table.EncodingRecords[i] = rec;
            }

            var charMap = new List<SequentialMapGroup>();
            for (var i = 0; i < table.NumTables; i++)
            {
                var rec = table.EncodingRecords[i];

                r.BaseStream.Position = rec.Offset;
                var fmt = r.ReadUInt16().Reverse();
                //if (fmt != 4 || fmt != 12)
                if (fmt != 12)
                    continue;

                r.BaseStream.Position = rec.Offset;
                //Format12(r, table, charMap);

                //if (rec.PlatformID == 3 && rec.EncodingID == 1)
                //    Format4(r, table, charMap);
                //else 
                if (rec.PlatformID == 3 && rec.EncodingID == 10)
                    Format12(r, table, charMap);
            }

            table.CharMaps = charMap.ToArray();
            return table;
        }

        static void Format4(BinaryReader r, IndexMappingTable table, List<SequentialMapGroup> chMap)
        {
            // Subtable format; set to 12
            //var format = r.ReadUInt16().Reverse();
        }

        static void Format12(BinaryReader r, IndexMappingTable table, List<SequentialMapGroup> chMap)
        {
            // Subtable format; set to 12
            var format = r.ReadUInt16().Reverse();
            if (format != 12)
                throw new Exception("OpenType：不支持此版本的子表(cmap)。");
            // Reserved; set to 0
            var reserved = r.ReadUInt16().Reverse();
            // Byte length of this subtable(including the header)
            var length = r.ReadUInt32().Reverse();
            // language field
            var language = r.ReadUInt32().Reverse();
            // Number of groupings which follow
            var numGroups = r.ReadUInt32().Reverse();
            // Array of SequentialMapGroup records
            for (var i = 0; i < numGroups; i++)
            {
                var group = new SequentialMapGroup();

                // First character code in this group
                group.StartCharCode = r.ReadUInt32().Reverse();
                // Last character code in this group
                group.EndCharCode = r.ReadUInt32().Reverse();
                // Glyph index corresponding to the starting character code
                group.StartGlyphID = r.ReadUInt32().Reverse();

                chMap.Add(group);
            }
        }
        #endregion cmap

        #region name
        // https://docs.microsoft.com/zh-cn/typography/opentype/spec/name

        public class NamingTable
        {
            // Format selector(0 / 1)
            public ushort Format { get; internal set; }
            // Number of name records
            public ushort Count { get; internal set; }
            // Offset to start of string storage (from start of table)
            public ushort StringOffset { get; internal set; }
            // The name records where count is the number of records
            public NameRecord[] NameRecords { get; internal set; }
            // Number of language-tag records.
            public ushort LangTagCount { get; internal set; }
            // The language-tag records where langTagCount is the number of records
            public LangTagRecord[] LangTagRecords { get; internal set; }
        }

        public class NameRecord
        {
            public int PlatformID;
            public int EncodingID;
            public int LanguageID;
            public int NameID;
            public int Length;
            public int Offset;
            // calculated
            public string Name;
        }

        public class LangTagRecord
        {
            public int Length;
            public int Offset;
            // calculated
            public string Name;
        }

        static object name(OpenTypeParser owner, BinaryReader r)
        {
            var table = new NamingTable();

            // Format selector 0 / 1
            table.Format = r.ReadUInt16().Reverse();
            if (table.Format != 0 && table.Format != 1)
                throw new Exception("OpenType：不支持此版本的文件(name)。");
            // Number of name records.
            table.Count = r.ReadUInt16().Reverse();
            // Offset to start of string storage (from start of table).
            table.StringOffset = r.ReadUInt16().Reverse();
            // The name records where count is the number of records.
            table.NameRecords = ReadNameRecords(r, table.Count);
            // 1.0
            if (table.Format == 1)
            {
                // Number of language-tag records.
                table.LangTagCount = r.ReadUInt16().Reverse();
                // The language-tag records where langTagCount is the number of records.
                table.LangTagRecords = ReadLangTagRecords(r, table.LangTagCount);
            }

            // read string values
            FillNameRecords(r, table.StringOffset, table.NameRecords);
            if (table.LangTagRecords != null)
                FillLangTagRecords(r, table.StringOffset, table.LangTagRecords);

            return table;
        }

        static NameRecord[] ReadNameRecords(BinaryReader r, int count)
        {
            var result = new NameRecord[count];
            for (var i = 0; i < count; i++)
            {
                result[i] = new NameRecord()
                {
                    PlatformID = r.ReadUInt16().Reverse(),
                    EncodingID = r.ReadUInt16().Reverse(), // Platform - specific encoding ID
                    LanguageID = r.ReadUInt16().Reverse(),
                    NameID = r.ReadUInt16().Reverse(),
                    Length = r.ReadUInt16().Reverse(),
                    Offset = r.ReadUInt16().Reverse(),
                };
            }

            return result;
        }

        static LangTagRecord[] ReadLangTagRecords(BinaryReader r, int count)
        {
            var result = new LangTagRecord[count];
            for (var i = 0; i < count; i++)
            {
                result[i] = new LangTagRecord()
                {
                    Length = r.ReadUInt16(),
                    Offset = r.ReadUInt16(),
                };
            }

            return result;
        }

        static void FillNameRecords(BinaryReader r, int baseOffset, NameRecord[] nameRecords)
        {
            for (var i = 0; i < nameRecords.Length; i++)
            {
                var rec = nameRecords[i];
                if (rec.Length == 0)
                    continue;

                r.BaseStream.Position = baseOffset + rec.Offset;
                var bytes = r.ReadBytes(rec.Length);
                rec.Name = bytes[0] == 0
                    ? Encoding.BigEndianUnicode.GetString(bytes)
                    : Encoding.ASCII.GetString(bytes);

                nameRecords[i] = rec;
            }
        }

        static void FillLangTagRecords(BinaryReader r, int baseOffset, LangTagRecord[] langTagRecords)
        {
            for (var i = 0; i < langTagRecords.Length; i++)
            {
                var rec = langTagRecords[i];

                r.BaseStream.Position = baseOffset + rec.Offset;
                var bytes = r.ReadBytes(rec.Length);
                rec.Name = Encoding.BigEndianUnicode.GetString(bytes);

                langTagRecords[i] = rec;
            }
        }
        #endregion name
    }
}
